# В предыдущих уроках мы рассмотрели 3 типа объектов asyncio:

# объекты корутины
# объекты Task
# объекты Future
# Эти три типа объектов называются awaitable объектами. Объединяет то, что их можно использовать
# с ключевым словом await для асинхронного выполнения кода.

# Это означает, что в любой момент, когда Вы используете await c awaitable объектами,  Вы говорите
# вашему коду: "Можешь передать управление обратно в цикл событий (event loop), и пусть он решает,
# какую задачу запустить дальше, пока я жду".

# Важно: 

# нужно запомнить, что в документации термин "корутина" используется для двух тесно связанных понятий:

# Функция-корутина: функция, определенная с помощью async def;
# Объект-корутина: объект, возвращаемый при вызове функции-корутины.

# async def my_coroutine():
#     # функция-корутина


# coro = my_coroutine() # объект-корутина

# Говоря об awaitable объектах мы всегда имеем ввиду объект-корутину. Также многие встроенные функции asyncio тоже
# возвращают объект корутину, например asyncio.sleep(x) возвращает объект корутины, который мы 
# ожидаем с помощью ключевого слова await. 

# В предыдущих уроках мы уже говорили о том, что перед каждым типом awaitable объектов нужно использовать
# ключевое слово  await. Рассмотрим еще раз несколько примеров, так как поведение разных awaitable объектов с
# ключевым словом await немного отличается. 

# Пример 1: использование await c корутиной
# import time
# import asyncio


# async def coro(num, seconds):
#     print(f"coro{num} начала свое выполнение")
#     await asyncio.sleep(seconds)
#     print(f"coro{num} выполнена за {seconds} секунду(ы)")


# async def main():
#     # Создание объектов корутин путем вызова корутинной функции.
#     coro1 = coro(1, 2)
#     coro2 = coro(2, 1)
#     # Запуск и ожидание выполнения объектов корутин.
#     await coro2
#     await coro1

# start = time.time()
# asyncio.run(main())
# print(f'Программа выполнена за {time.time()-start:.3f} секунд(ы)')

# Вывод:

# coro2 начала свое выполнение
# coro2 выполнена за 1 секунду(ы)
# coro1 начала свое выполнение
# coro1 выполнена за 2 секунду(ы)
# Программа выполнена за 3.011 секунд(ы)
# Обратите внимание на то, что хотя порядок создания корутин сначала coro1, затем coro2, порядок выполнения
# следует порядку их вызова через await. Это связано с тем, что выполнение каждой корутины начинается только
# тогда, когда мы явно запускаем корутину через await coro.

# Пример 2: использование await c объектом Task
# import asyncio
# import time


# async def coro(num, seconds):
#     print(f"Задача{num} начала свое выполнение")
#     await asyncio.sleep(seconds)
#     print(f"Задача{num} выполнена за {seconds} секунду(ы)")


# async def main():
#     # Создание задач из корутины.
#     task1 = asyncio.create_task(coro(1, 2))
#     task2 = asyncio.create_task(coro(2, 1))
#     # Запуск и ожидание выполнения задач.
#     await task2
#     await task1

# start = time.time()
# asyncio.run(main())
# print(f'Программа выполнена за {time.time()-start:.3f} секунд(ы)')

# Вывод:

# Задача1 начала свое выполнение
# Задача2 начала свое выполнение
# Задача2 выполнена за 1 секунду(ы)
# Задача1 выполнена за 2 секунду(ы)
# Программа выполнена за 2.011 секунд(ы)
# Обратите внимание, что теперь порядок вывода другой. Задачи начинают выполняться в порядке создания, и их выполнение
# происходит асинхронно. Общее время выполнения немного превышает время выполнения самой "долгой" задачи. Это связано
# с тем, что задача при создании добавляется в цикл событий и начинает выполнение при переключении контекста. То есть обе
# созданные задачи начнут выполнение при первом await. 

# Аналогия из реальной жизни и Awaitable объекты:
# Допустим, вы являетесь менеджером в ресторане, и ваша задача - обеспечить эффективное выполнение заказов клиентов.
# В данной аналогии Awaitable объекты будут схожи с различными задачами, которые нужно выполнить для завершения каждого
# заказа. Рассмотрим каждый из Awaitable объектов в контексте ресторана:

# Корутины: представьте, что корутины - это сотрудники кухни, которые готовят блюда. Каждый сотрудник работает над
# своим заказом, и при этом, в отличие от обычных функций, они могут прервать свою работу и уступить место у плиты
# (передать очередь) другому сотруднику, если им нужно дождаться, пока что-то приготовится, остынет или прочее. 
# Это позволяет им работать над другими заказами, пока ожидают выполнения текущей задачи (например, он ждёт, пока закипит вода).

# Объекты Task: эти объекты можно сравнить с заказами, которые сотрудники выполняют. Когда клиент делает заказ, 
# менеджер создает задачу Task и назначает ее сотруднику-кулинару (корутине). Задача отслеживает выполнение заказа
# и его результат, позволяя менеджеру контролировать прогресс и знать, когда заказ готов.

# Объекты Future: в нашей аналогии объекты Future могут быть представлены как прогнозы или ожидания относительно задач,
# связанных с заказами. Например, менеджер ресторана может использовать объект Future для ожидания поставки ингредиентов.
# Future представляет собой контракт, который обязует поставщика предоставить ингредиенты в определенное время. 
# Когда поставка будет осуществлена, объект Future обновляется, и менеджер может продолжить выполнять свои обязанности,
# опираясь на выполненный контракт.